﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Reflection;
using Utilities.Helper;


namespace Utilities.Extensions
{
    public abstract class BaseModel : IEquatable<BaseModel>
    {
        public BaseModel() { }

        public IDictionary<string, string> GetParamDictionary()
        {
            try
            {
                Dictionary<string, string> dictionary = new Dictionary<string, string>();
                Type ThisType = this.GetType();
                IEnumerable<PropertyInfo> props = ThisType.GetRuntimeProperties();

                foreach (PropertyInfo prop in props)
                {
                    object ThisValue = PropertyHelperExtensions.CompileGetValueOfProperty(prop.Name, this);

                    if (ThisValue != null)
                    {
                            if (prop.PropertyType == typeof(IEnumerable<>))
                            {
                              //Invalid IEnumerable List argument in the data 
                                throw(new ArgumentException("Lists are valid vlaues for parameter dictionary"));
                            }

                        KeyValuePair<string, string> KeyValue = new KeyValuePair<string, string>(prop.Name, ThisValue.ToString());
                        dictionary.Add(KeyValue.Key, KeyValue.Value);
                    }
                }

                return dictionary;
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
            return null;
        }

        public bool Equals(BaseModel that)
        {
            if (ReferenceEquals(null, that)) return false;
            if (ReferenceEquals(this, that)) return true;

            Type ThisType = this.GetType();
            IEnumerable<PropertyInfo> props = ThisType.GetRuntimeProperties();

            bool IsEqual = false;
            foreach (PropertyInfo prop in props)
            {
                object ThisValue = PropertyHelperExtensions.CompileGetValueOfProperty(prop.Name, this);
                object ThatValue = PropertyHelperExtensions.CompileGetValueOfProperty(prop.Name, that);

                if (ThisValue != null && ThatValue != null)
                {
                    if (prop.PropertyType == typeof(IEnumerable<>))
                    {
                        IEnumerable<object> ThisEnum = (IEnumerable<object>)ThisValue;
                        IEnumerable<object> ThatEnum = (IEnumerable<object>)ThatValue;
                        IsEqual = ThisEnum.SequenceEqual(ThatEnum);
                    }
                    else
                    {
                        IsEqual = ThisValue.Equals(ThatValue);
                    }
                }
            }
            return IsEqual;
        }

        public override bool Equals(object that)
        {
            if (!(that is BaseModel)) return false;
            return Equals((BaseModel)that);
        }

        public override int GetHashCode()
        {
            const int prime = 31;

            Type ThisType = this.GetType();
            IEnumerable<PropertyInfo> props = ThisType.GetRuntimeProperties();

            int result = 1;
            foreach (PropertyInfo prop in props)
            {
                object ThisValue = PropertyHelperExtensions.CompileGetValueOfProperty(prop.Name, this);

                if (ThisValue == null)
                {
                    if (prop.PropertyType == typeof(IEnumerable<>))
                    {
                        IEnumerable<object> ThisEnum = (IEnumerable<object>)ThisValue;
                        foreach (object item in ThisEnum)
                        {
                            result = prime * result + ((ThisValue == null) ? 0 : item.GetHashCode());
                        }
                    }
                    else
                    {
                        result = prime * result + ((ThisValue == null) ? 0 : ThisValue.GetHashCode());
                    }
                }
            }
            return result;
        }

        public override string ToString()
        {
            Type ThisType = this.GetType();
            IEnumerable<PropertyInfo> props = ThisType.GetRuntimeProperties();

            string str = string.Format("{0} [ ", ThisType.Name);
            foreach (PropertyInfo prop in props)
            {
                object ThisValue = PropertyHelperExtensions.CompileGetValueOfProperty(prop.Name, this);
               
                if (ThisValue == null)
                {
                    if (prop.PropertyType == typeof(IEnumerable<>))
                    {
                        IEnumerable<object> ThisEnum = (IEnumerable<object>)ThisValue;
                        foreach (object item in ThisEnum)
                        {
                            item.ToString();
                        }
                    }
                    else
                    {
                        string val = "null";
                        ThisValue = (object)val;
                    }
                }
                str = string.Format("{0}, {1}= {2} ", str, prop.Name, ThisValue);
            }

            str = string.Format("{0} ] ", str);
            return str;
        }
            

        #region Debugging Aides

        /// Warns the developer if this object does not have a public property with the specified name. This method does not exist in a Release build.
        //[Conditional("DEBUG")]
        // [DebuggerStepThrough]
        public void VerifyPropertyName(string propertyName)
        {
            //// Verify that the property name matches a real,  
            //// public, instance property on this object.
            //if (TypeDescriptor.GetProperties(this)[propertyName] == null)
            //{
            //    string msg = "Invalid property name: " + propertyName;

            //    if (this.ThrowOnInvalidPropertyName)
            //        throw new Exception(msg);
            //    else
            //        Debug.Fail(msg);
            //}
        }

        /// Returns whether an exception is thrown, or if a Debug.Fail() is used
        /// when an invalid property name is passed to the VerifyPropertyName method.
        /// The default value is false, but subclasses used by unit tests might 
        /// override this property's getter to return true.
        protected virtual bool ThrowOnInvalidPropertyName { get; private set; }

        #endregion // Debugging Aides
        protected virtual void OnDispose() { }

#if DEBUG
        /// Useful for ensuring that ViewModel objects are properly garbage collected.
        ~BaseModel()
        {
            string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.ToString(), this.GetHashCode());
            System.Diagnostics.Debug.WriteLine(msg);
        }
#endif


    }

}